﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Web;
using System.Web.Mvc;
using MagickNet;
using MagickServer.Services;

namespace MagickServer.Controllers {
    /// <summary>
    /// Does the actual image processing
    /// </summary>
    [HandleError]
    public class ImageController : Controller {
        private readonly IImageSource imageSource;

        public ImageController(IImageSource imageSource) {
            this.imageSource = imageSource;
        }

        public class OperationParameter {
            public string Name { get; set; }
            public string Value { get; set; }
        }

        public class Operation {
            public string Op { get; set; }
            public OperationParameter[] Parameters { get; set; }
        }

        /// <summary>
        /// Handles URLs like http://localhost/image/blur?file=/DSC02568.JPG&radius=5&sigma=5
        /// </summary>
        /// <param name="actionName"></param>
        protected override void HandleUnknownAction(string actionName) {
            var file = RouteData.Values["file"] as string;
            WithImageDo(file, img => {
                var paramDict = new Dictionary<string, object>();
                Request.QueryString.CopyTo(paramDict);
                paramDict.Remove("file");
                ApplyOperation(img, new KeyValuePair<string, Dictionary<string, object>>(actionName, paramDict));                
            });
        }

        private void WithImageDo(string file, Action<Image> f) {
            using (var img = imageSource.GetImage(file)) {
                if (img == null)
                    throw new HttpException(404, "Image not found");

                f(img);

                Response.ContentType = "image/jpeg";
                img.Write(Response.OutputStream, "JPEG");
            }            
        }

        /// <summary>
        /// Handles URLs like http://localhost/image/index?file=/DSC02568.JPG&ops[0].Op=Blur&ops[0].Parameters[0].Name=radius&ops[0].Parameters[0].Value=100&ops[0].Parameters[1].Name=sigma&ops[0].Parameters[1].Value=100
        /// </summary>
        /// <param name="file"></param>
        /// <param name="ops"></param>
        /// <returns></returns>
        public ActionResult Index(string file, Operation[] ops) {
            WithImageDo(file, img => {
                var opsDict = GetOpsDict(ops);
                ApplyOperations(img, opsDict);                
            });
            return null;
        }

        private void ApplyOperations(Image img, IEnumerable<KeyValuePair<string, Dictionary<string, object>>> opsDict) {
            foreach (var o in opsDict) {
                ApplyOperation(img, o);
            }
        }

        private void ApplyOperation(Image img, KeyValuePair<string, Dictionary<string, object>> o) {
            var method = GetMethod(o);
            if (method == null)
                throw new HttpException(500, "Method not found");
            var parameters = GetParameters(method, o.Value).ToArray();
            method.Invoke(img, parameters);
        }

        private MethodInfo GetMethod(KeyValuePair<string, Dictionary<string, object>> o) {
            return GetMethod(o.Key, o.Value.Count);
        }

        private MethodInfo GetMethod(string name, int parameterCount) {
            return typeof (Image).GetMethods()
                .Where(m => m.Name.ToLowerInvariant() == name.ToLowerInvariant())
                .FirstOrDefault(m => m.GetParameters().Length == parameterCount);
        }

        private IDictionary<string, Dictionary<string, object>> GetOpsDict(IEnumerable<Operation> ops) {
            var opsDict = new Dictionary<string, Dictionary<string, object>>();
            foreach (var o in ops) {
                var param = new Dictionary<string, object>();
                if (o.Parameters != null)
                    foreach (var p in o.Parameters) {
                        param[p.Name.ToLowerInvariant()] = p.Value;
                    }
                opsDict[o.Op] = param;
            }
            return opsDict;
        }

        public IEnumerable<object> GetParameters(MethodInfo method, IDictionary<string, object> parameters) {
            foreach (var p in method.GetParameters()) {
                var pname = p.Name.ToLowerInvariant();
                if (parameters.ContainsKey(pname))
                    yield return Convert.ChangeType(parameters[pname], p.ParameterType);
            }
        }
    }
}